package com.zzyl.job;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSON;
import cn.hutool.json.JSONUtil;
import com.zzyl.constant.CacheConstants;
import com.zzyl.dto.AlertNotifyDto;
import com.zzyl.entity.AlertData;
import com.zzyl.entity.AlertRule;
import com.zzyl.mapper.AlertDataMapper;
import com.zzyl.mapper.AlertRuleMapper;
import com.zzyl.mapper.DeviceMapper;
import com.zzyl.mapper.UserRoleMapper;
import com.zzyl.properties.AlertInfoProperties;
import com.zzyl.socket.WebSocketServer;
import com.zzyl.vo.DeviceDataVo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import java.time.Duration;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

@Component
public class AlertJob {
    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    @Autowired
    private AlertRuleMapper alertRuleMapper;
    @Autowired
    private DeviceMapper deviceMapper;
    @Autowired
    private UserRoleMapper userRoleMapper;
    @Autowired
    private AlertInfoProperties alertInfoProperties;
    @Autowired
    private AlertDataMapper alertDataMapper;
    @Autowired
    private WebSocketServer webSocketServer;

    // 设置定时任务，每隔10秒执行一次
    @Scheduled(fixedRate = 10000)
    public void deviceAlertHandler(){
        // 1、查询所有的报警规则
      List<AlertRule> alertRuleList = alertRuleMapper.selectAll();
      if(CollectionUtil.isEmpty(alertRuleList)){
          return;
      }
      // 2、获取所有的报警数据
        List<Object> jsonDataList = stringRedisTemplate.opsForHash().values(CacheConstants.IOT_DEVICE_LAST_DATA);
      if(CollectionUtil.isEmpty(jsonDataList)){
          return;
      }
      // 3、对报警数据进行格式化处理
        // 流的扁平化处理 -- 获取到每一个设备上报的每一条指标数据
        List<DeviceDataVo> dataVoList = jsonDataList.stream()
                // 将Redis中获取到的每条数据转化为设备数据的List集合
                .map(data -> JSONUtil.toList(data.toString(), DeviceDataVo.class))
                // 将内层流中集合的数据，合并到外层流中
                .flatMap(list -> list.stream())
                // 采集流中的数据
                .collect(Collectors.toList());
        // 4、遍历每一条报警数据，匹配需要执行的规则
        dataVoList.forEach(deviceDataVo -> matchAndHandlerAlertRule(deviceDataVo,alertRuleList));
    }

    // 从所有的报警规则中，匹配适用于当前数据的报警规则，并执行报警
    private void matchAndHandlerAlertRule(DeviceDataVo deviceDataVo, List<AlertRule> alertRuleList) {
        // 1、判断设备上报数据的时间，如果时间大于一分钟代表历史数据，不需要报警
        LocalDateTime alarmTime = deviceDataVo.getAlarmTime();
        if(alarmTime.isBefore(LocalDateTime.now().minus(1, ChronoUnit.MINUTES))){
            return;
        }
        // 2、从所有的报警规则中，找到使用于当前数据的报警规则
        List<AlertRule> ruleList = alertRuleList.stream()
                // 过滤设备
                .filter(alertRule -> StrUtil.equals(alertRule.getProductKey(), deviceDataVo.getProductKey()))
                // 过滤指标
                .filter(alertRule -> StrUtil.equals(alertRule.getFunctionId(), deviceDataVo.getFunctionId()))
                // 过滤匹配的规则
                .filter(alertRule -> StrUtil.equals(alertRule.getIotId(), "-1") || StrUtil.equals(alertRule.getIotId(), deviceDataVo.getIotId()))
                .collect(Collectors.toList());
        System.out.println(ruleList);
        if(CollectionUtil.isEmpty(ruleList)){
            return;
        }
        // 3、遍历匹配规则
        ruleList.forEach(alertRule -> {
            // 判断是否达到报警条件，如果达到报警条件，进行报警
            Boolean isNeedAlert=isNeeHandleAlert(deviceDataVo,alertRule);
            if(isNeedAlert){
                    handleAlert(deviceDataVo,alertRule);
            }
        });
    }

    /**
     * 处理报警（将报警数据写到数据库中）
     */
    private void handleAlert(DeviceDataVo deviceDataVo, AlertRule alertRule) {
        // 1、清空持续周期次数
        clearTriggerCount(deviceDataVo,alertRule);
        // 2、保存沉默周期标识
        setSilentFlag(deviceDataVo, alertRule);
        // 3、获取报警通知人
        List<Long> userIdList;
        if(deviceDataVo.getPhysicalLocationType() == -1){
            // 3、1设备绑定老人
             userIdList = deviceMapper.queryNursingIdsByIotIdWithElder(deviceDataVo.getIotId());
        }else if(deviceDataVo.getPhysicalLocationType() == 2){
            // 3、2设备绑定床位
             userIdList = deviceMapper.queryNursingIdsByIotIdWithBed(deviceDataVo.getIotId());
        }else {
            // 3、3 固定设备
             userIdList = userRoleMapper.queryByRoleName(alertInfoProperties.getDeviceMaintainerRole());
        }
        // 所有的报警通知，都要发送给超级管理员
        List<Long> manageIdList = userRoleMapper.queryByRoleName(alertInfoProperties.getManagerRole());
        //  CollectionUtil.addAll()把两个集合中的数据合并到一起，并去重再返回一个list类型
        userIdList = CollectionUtil.addAll(userIdList, manageIdList).stream().distinct().collect(Collectors.toList());
        // 4、为报警通知人保存报警数据
        insertAlertData(deviceDataVo,alertRule,userIdList);
    }

    /**
     * 保存报警数据到数据库中
     */
    private void insertAlertData(DeviceDataVo deviceDataVo, AlertRule alertRule, List<Long> userIdList) {
        // 1、封装基础的报警数据到实体类
        AlertData alertData = BeanUtil.toBean(deviceDataVo, AlertData.class);
        alertData.setAlertRuleId(alertRule.getId());
        String reason = StrUtil.format("{} {} {} {} 持续触发{}个周期时发生报警",
                alertRule.getProductName(),
                alertRule.getFunctionName(),
                alertRule.getOperator(),
                alertRule.getValue(),
                alertRule.getDuration());
        alertData.setAlertReason(reason);
        alertData.setType(alertRule.getAlertDataType());
        alertData.setStatus(0);
        // 2、遍历用户Id集合，为每一个需要接收报警信息的用户保存报警数据
        List<AlertData> alertDataList = userIdList.stream().map(userId -> {
            // 1和2效果一样，都是基于初始对象，获取新对象
            // 1、AlertData newAlertData = BeanUtil.toBean(alertData, AlertData.class);
            // 2、 ObjectUtil.clone(obj) 克隆一个新对象
            // 深克隆：创建一个跟原有对象数据属性完全相同的新对象
            // 浅克隆：创建一个对象，新对象和旧对象的地址值相同（本质上还是同一个）
            AlertData clone = ObjectUtil.clone(alertData);
            clone.setUserId(userId);
            return clone;
        }).collect(Collectors.toList());
        alertDataMapper.batchInsert(alertDataList);

        // 推送报警数据到用户浏览器
        AlertNotifyDto alertNotifyDto = BeanUtil.toBean(alertData, AlertNotifyDto.class);
        alertNotifyDto.setAlertDataType(alertRule.getAlertDataType());
        alertNotifyDto.setVoiceNotifyStatus(1);
        alertNotifyDto.setNotifyType(1);
        webSocketServer.sendMessageUserList(userIdList, JSONUtil.toJsonStr(alertNotifyDto));
    }

    /**
     * 保存沉默周期标识
     */
    private void setSilentFlag(DeviceDataVo deviceDataVo, AlertRule alertRule) {
        String key = CacheConstants.ALERT_SILENT_CYCLE + deviceDataVo.getIotId() + ":" + deviceDataVo.getFunctionId() + ":" + alertRule.getId();
        stringRedisTemplate.opsForValue().set(key, deviceDataVo.getDataValue(), Duration.ofMinutes(alertRule.getAlertSilentPeriod()));
    }

    // 判断是否需要执行报警
    private Boolean isNeeHandleAlert(DeviceDataVo deviceDataVo, AlertRule alertRule) {
        // 1、判断数据上报的时间是否在有效时间范围内
        boolean isInEffective=isInEffectivePeriod(deviceDataVo,alertRule);
        if(!isInEffective){
            return false;
        }
        // 2、判断上报数据是否达到报警阈值
        boolean isReachThreshold=isValueReachThreshold(deviceDataVo,alertRule);
        if(!isReachThreshold){
            clearTriggerCount(deviceDataVo, alertRule);
            return false;
        }
        // 3、判断是否在沉默周期范围内
        boolean isInSilent=isInSilentPeriod(deviceDataVo,alertRule);
        if(!isInSilent){
            return false;
        }
        // 4、判断触发次数是否达到持续周期
        boolean isReachTrigger=isReachTriggerCount(deviceDataVo,alertRule);
        if(!isReachTrigger){
            return false;
        }
        return true;
    }

    /**
     * 情况报警触发次数
     */
    private void clearTriggerCount(DeviceDataVo deviceDataVo, AlertRule alertRule) {
        String key = CacheConstants.ALERT_TRIGGER_COUNT + deviceDataVo.getIotId() + ":" + deviceDataVo.getFunctionId() + ":" + alertRule.getId();
        stringRedisTemplate.delete(key);
    }

    /**
     * 判断数据上报的时间是否在有效时间范围内
     */
    private boolean isInEffectivePeriod(DeviceDataVo deviceDataVo, AlertRule alertRule) {
        if(StrUtil.isEmpty(alertRule.getAlertEffectivePeriod())){   // AlertEffectivePeriod 报警生效时段
            return false;
        }
        // 报警有效期开始时间和结束时间
        String[] period = alertRule.getAlertEffectivePeriod().split("~");
        LocalTime startTime = LocalTime.parse(period[0], DateTimeFormatter.ofPattern("HH:mm:ss"));
        LocalTime endTime = LocalTime.parse(period[1], DateTimeFormatter.ofPattern("HH:mm:ss"));
        // 报警时间
        LocalTime localTime = deviceDataVo.getAlarmTime().toLocalTime();
        // 判断在不在有效时段
        if(localTime.isAfter(endTime) || localTime.isBefore(startTime) ){
            return false;
        }
        return true;
    }

    /**
     * 判断上报数据是否达到报警阈值
     */
    private boolean isValueReachThreshold(DeviceDataVo deviceDataVo, AlertRule alertRule) {
        Float deviceFloat = Float.valueOf(deviceDataVo.getDataValue());
        /**
         * Float.compare(float x, float y) 方法
         * 两个浮点数 x 和 y
         * 如果 x 小于 y，则返回负整数（通常是 -1）。
         * 如果 x 等于 y（考虑浮点数的精度），则返回零。
         * 如果 x 大于 y，则返回正整数（通常是 1）。
         */
        int compare = Float.compare(deviceFloat, alertRule.getValue());
        if((StrUtil.equals(alertRule.getOperator(),">=") && compare >= 0) || (StrUtil.equals(alertRule.getOperator(),"<") && compare < 0)){
            //设备上报数据超出阈值
            return true;
        }
        return false;
    }

    /**
     * 判断是否在沉默周期范围内
     */
    private boolean isInSilentPeriod(DeviceDataVo deviceDataVo, AlertRule alertRule) {
        // 某一个设备的某一个指标是否执行过某种报警规则
        String key = CacheConstants.ALERT_SILENT_CYCLE + deviceDataVo.getIotId() + ":" + deviceDataVo.getFunctionId() + ":" + alertRule.getId();
        // 如果Redis中存在这个Key ,代表在沉默周期内,之前已经报过警
        return stringRedisTemplate.hasKey(key);
    }

    /**
     * 判断触发次数是否达到持续周期
     */
    private boolean isReachTriggerCount(DeviceDataVo deviceDataVo, AlertRule alertRule) {
        // 某一个设备的某一个指标匹配某种报警规则的次数
        String key = CacheConstants.ALERT_TRIGGER_COUNT + deviceDataVo.getIotId() + ":" + deviceDataVo.getFunctionId() + ":" + alertRule.getId();
        // 对某一个key的值进行自增，返回自增之后的结果
        Long increment = stringRedisTemplate.opsForValue().increment(key);
        Integer duration = alertRule.getDuration(); //持续周期
        if(Objects.equals(increment.intValue(),duration)){
            return true;
        }
        return  false;
    }
}
